package com.whale.kernel.service.hibernate;

import static javax.xml.stream.XMLStreamConstants.END_DOCUMENT;
import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.whale.kernel.domain.HtmlMenu;
import com.whale.kernel.domain.HtmlPage;
import com.whale.kernel.exception.HtmlPageException;
import com.whale.kernel.exception.ResourcesNotFoundException;
import com.whale.kernel.model.pagination.PagedList;
import com.whale.kernel.model.pagination.Step;
import com.whale.kernel.service.HtmlMenuService;
import com.whale.kernel.service.HtmlPageService;

public class HibernateHtmlPageService extends AbstractHibernateService
		implements HtmlPageService {

	private Logger logger = LoggerFactory
			.getLogger(HibernateHtmlPageService.class);

	private final String pageNavigationConfig = "/resources/page-navigation.xml";

	private final static String MENU_TAG = "menu";
	private final static String PATH_ATTR = "path";
	private final static String PAGE_TAG = "page";
	private final static String PAGE_ID_ATTR = "pageid";
	private final static String TITLE_ATTR = "title";
	private final static String PAGE_LOCATION_ATTR = "pagelocation";
	private final static String ICON_LOCATION_ATTR = "iconlocation";
	private final static String SHORT_DESCRIPTION_TAG = "shortdescription";

	private HtmlMenuService htmlMenuService;

	public void setHtmlMenuService(HtmlMenuService htmlMenuService) {
		this.htmlMenuService = htmlMenuService;
	}

	public void reInitilizeHtmlPages(ServletContext ctx) {
		InputStream istream = ctx.getResourceAsStream(pageNavigationConfig);
		if (istream == null) {
			throw new ResourcesNotFoundException(pageNavigationConfig);
		}
		try {
			reloadPages(istream);
		} catch (XMLStreamException e) {
			logger.error(e.getMessage(), e);
			throw new HtmlPageException(e.getMessage(), e);
		} finally {
			try {
				istream.close();
			} catch (IOException e) {
				throw new HtmlPageException(e.getMessage(), e);
			}
		}

	}

	private void reloadPages(InputStream istream) throws XMLStreamException {
		XMLInputFactory xmlif = createXMLInputFactory();
		XMLStreamReader xmlr = xmlif.createXMLStreamReader(istream);

		// clean all HtmlPage items
		executeUpdate("delete from HtmlPage");

		try {
			int event = xmlr.getEventType();
			while (xmlr.hasNext()) {
				switch (event) {
				case START_ELEMENT:
					if (xmlr.getLocalName().equals(MENU_TAG)) {
						parseMenu(xmlr);
					}
					break;
				case END_DOCUMENT:
					return;
				}
				event = xmlr.next();
			}
		} finally {
			xmlr.close();
		}

		return;
	}

	private void parseMenu(XMLStreamReader xmlr) throws XMLStreamException {
		Map<String, String> attributes = new HashMap<String, String>();
		for (int i = 0; i < xmlr.getAttributeCount(); i++) {
			attributes.put(xmlr.getAttributeLocalName(i),
					xmlr.getAttributeValue(i));
		}

		HtmlMenu pageMenu = htmlMenuService.findHtmlMenu(attributes
				.get(PATH_ATTR));

		int event = xmlr.next();
		do {
			switch (event) {
			case START_ELEMENT:
				if (xmlr.getLocalName().equals(PAGE_TAG)) {
					parsePage(xmlr, pageMenu);
				}
				break;
			case END_ELEMENT:
				if (xmlr.getLocalName().equals(MENU_TAG)) {
					return;
				}
				break;
			}
			event = xmlr.next();
		} while (xmlr.hasNext());

	}

	private void parsePage(XMLStreamReader xmlr, HtmlMenu htmlMenu)
			throws XMLStreamException {
		Map<String, String> attributes = new HashMap<String, String>();
		for (int i = 0; i < xmlr.getAttributeCount(); i++) {
			attributes.put(xmlr.getAttributeLocalName(i),
					xmlr.getAttributeValue(i));
		}

		HtmlPage htmlPage = new HtmlPage();
		htmlPage.setMenu(htmlMenu);
		htmlPage.setPageId(attributes.get(PAGE_ID_ATTR));
		htmlPage.setTitle(attributes.get(TITLE_ATTR));
		htmlPage.setIconLocation(attributes.get(ICON_LOCATION_ATTR));
		htmlPage.setPageLocation(attributes.get(PAGE_LOCATION_ATTR));

		int event = xmlr.next();
		do {
			switch (event) {
			case START_ELEMENT:
				if (xmlr.getLocalName().equals(SHORT_DESCRIPTION_TAG)) {
					htmlPage.setShortDescription(xmlr.getElementText());
				}
				break;
			case END_ELEMENT:
				if (xmlr.getLocalName().equals(PAGE_TAG)) {
					hibernateTemplate.save(htmlPage);
					return;
				}
				break;
			}
			event = xmlr.next();
		} while (xmlr.hasNext());
	}

	public HtmlPage findHtmlPage(String path, String pageId) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("pageId", pageId);
		params.put("htmlMenu", htmlMenuService.findHtmlMenu(path));
		HtmlPage page = this.getUniqueQueryResult(HtmlPage.class,
				"from HtmlPage where pageId =:pageId and menu =:htmlMenu",
				params);
		populatePagePath(page);
		return page;
	}

	public PagedList<HtmlPage> findHtmlPages(String path, Step step) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("htmlMenu", htmlMenuService.findHtmlMenu(path));
		String sql = "from HtmlPage where menu =:htmlMenu";
		List<HtmlPage> pages = this.getQueryResult(HtmlPage.class,
				"from HtmlPage where menu =:htmlMenu", params,
				step.getOffset(), step.getMax());
		populatePagesPath(pages);
		Long total = this.getUniqueQueryResult(Long.class, "select count(id) "
				+ sql, params);
		return new PagedList<HtmlPage>(total, pages);
	}

	public PagedList<HtmlPage> findSubMenuPagesUnderTopMenu(String topMenuPath,
			Step step) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("topMenuPath", topMenuPath);
		String sql = "from HtmlPage where (menu.parentMenu is not null)"
				+ " and (menu.parentMenu.path =:topMenuPath)";
		List<HtmlPage> pages = this.getQueryResult(HtmlPage.class, sql, params,
				step.getOffset(), step.getMax());
		populatePagesPath(pages);
		Long total = this.getUniqueQueryResult(Long.class, "select count(id) "
				+ sql, params);
		return new PagedList<HtmlPage>(total, pages);
	}

	private void populatePagesPath(List<HtmlPage> pages) {
		for (HtmlPage page : pages) {
			populatePagePath(page);
		}
	}

	private void populatePagePath(HtmlPage page) {
		page.setPagePath(page.getMenu().getPath() + "/" + page.getPageId());
	}

}
